package fr.neyb.wesb.core.data.io.impl;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import fr.neyb.framework.util.CharSequences.Formatter;
import fr.neyb.framework.util.CharSequences.Parser;
import fr.neyb.framework.util.Tools;
import fr.neyb.wesb.core.data.content.Field;
import fr.neyb.wesb.core.data.content.Record;
import fr.neyb.wesb.core.data.content.TextableField;
import fr.neyb.wesb.core.data.content.impl.sb.SBField;
import fr.neyb.wesb.core.data.content.impl.sb.SBRecord;
import fr.neyb.wesb.core.data.descriptors.field.FieldDescriptor;
import fr.neyb.wesb.core.data.descriptors.record.RecordDescriptor;
import fr.neyb.wesb.core.data.io.IOTextFormat;

public class SeparatorIOTextFormat implements IOTextFormat {

	private final static class DefaultTextableFieldParser implements Parser<Field> {
		private final FieldDescriptor fd;

		public DefaultTextableFieldParser(FieldDescriptor fd) {
			this.fd = fd;
		}

		@Override
		public Field parse(CharSequence source) {
			return new SBField(source.toString(), fd);
		}
	}
	
	private final static Formatter<Field> defaultFormatter = new Formatter<Field>() {
		@Override
		public String format(Field source) {
			if (source instanceof TextableField) {
				return ((TextableField) source).toString();
			} else {
				return "";
			}
		}
	};

	private final static class StringDetector {
		private final CharSequence searched;
		private final StringBuilder current = new StringBuilder();

		public StringDetector(CharSequence s) {
			Tools.checkNotNull(s);
			if (s.length() == 0)
				throw new IllegalArgumentException();

			this.searched = s;
		}

		public boolean test(char c) {
			if (searched.charAt(current.length()) == c) {
				current.append(c);

				if (searched.length() == current.length()) {
					current.delete(0, current.length());
					return true;
				} else {
					return false;
				}
			} else {
				current.delete(0, current.length());
				return false;
			}
		}

	}

	private final String fieldSep;
	private final String RecordSep;
	private final RecordDescriptor rd;
	private final List<Parser<Field>> parsers = new ArrayList<>();
	private final List<Formatter<Field>> formatters = new ArrayList<>();

	public SeparatorIOTextFormat(String fieldSep, String RecordSep,
			RecordDescriptor rd) {
		this.fieldSep = fieldSep;
		this.RecordSep = RecordSep;
		this.rd = rd;
		for (FieldDescriptor fieldDescriptor : rd) {
			parsers.add(new DefaultTextableFieldParser(fieldDescriptor));
			formatters.add(defaultFormatter);
		}
	}

	public SeparatorIOTextFormat(String fieldSep, String RecordSep,
			RecordDescriptor rd, List<Parser<Field>> parsers,
			List<Formatter<Field>> formatters) {
		// TODO compare parsers,formatters and rd size
		this.fieldSep = fieldSep;
		this.RecordSep = RecordSep;
		this.rd = rd;
		this.parsers.addAll(parsers);
		this.formatters.addAll(formatters);
	}

	@Override
	public Record read(Reader r) throws IOException {
		StringBuffer buffer = new StringBuffer();
		StringDetector recordEndDetector = new StringDetector(RecordSep);
		StringDetector fieldEndDetector = new StringDetector(fieldSep);

		List<Field> result = new ArrayList<>();

		int currCode;
		char currChar;
		while ((currCode = r.read()) >= 0) {
			currChar = (char) currCode;

			buffer.append(currChar);

			if (recordEndDetector.test(currChar)) {
				addFieldWhen(result, buffer, RecordSep);
				break;
			}
			if (fieldEndDetector.test(currChar)) {
				addFieldWhen(result, buffer, fieldSep);
			}

		}
		if (currCode < 0) {
			// end reached
			if (result.size() == 0 && buffer.length() == 0) {
				// nothhing has been read
				return null;
			} else {
				// something is probably in buffer
				addFieldWhen(result, buffer, "");
			}
		}
		return new SBRecord(result, rd);
	}

	@Override
	public void Write(Writer w, Record record) throws IOException {
		List<Field> fields = record.getFields();

		int minSize = Tools.min(formatters.size(), fields.size());

		for (int i = 0; i < minSize; i++) {
			if (i > 0)
				w.write(fieldSep);
			w.write(formatters.get(i).format(fields.get(i)));
		}

		w.write(RecordSep);
	}

	/**
	 * remove detected from the end of the buffer (no check), add a field in
	 * target and clean buffer
	 * 
	 * @param target
	 * @param sourceBuffer
	 * @param detected
	 * @param fieldIndex
	 */
	private void addFieldWhen(List<Field> target, StringBuffer sourceBuffer,
			String detected) {
		sourceBuffer.delete(sourceBuffer.length() - detected.length(),
				sourceBuffer.length());
		if (canAddTo(target)) {
			Field toAdd = parsers.get(target.size()).parse(sourceBuffer);
			target.add(toAdd);
		}
		sourceBuffer.delete(0, sourceBuffer.length());
	}

	private boolean canAddTo(List<Field> target) {
		return target.size() < rd.size();
	}

	@Override
	public RecordDescriptor getDescriptor() {
		return rd;
	}

}
